package wangjg.mda.tra;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import wangjg.commons.bean.BeanUtil;
import wangjg.mda.context.ModelContext;
import wangjg.mda.model.obj.Element;
import wangjg.mda.model.obj.ManyToMany;
import wangjg.mda.model.obj.Method;
import wangjg.mda.model.obj.Model;
import wangjg.mda.model.obj.ModelDataType;
import wangjg.mda.model.obj.ModelField;
import wangjg.mda.model.obj.Parameter;
import wangjg.mda.model.obj.Reference;

/**
 * 
 * @author wangjg
 *
 */
public class ModelUtil {
	private ModelContext ctx;
	
	public Model findModel(String name){
		return ctx.findModel(name);
	}
	
	public List<String> getImportList(Model model){
		String curPkg = model.getPackage();
		List<String> list = new ArrayList<String>();
		String type = model.getParent();
		this.handleImport(type, curPkg, list);
		for(ModelField field:model.getFields()){
			type = field.getType();
			this.handleImport(type, curPkg, list);
		}
		for(Method method:model.getMethods()){
			type = method.getReturnType();
			this.handleImport(type, curPkg, list);
			for(Parameter param:method.getParameters()){
				type = param.getType();
				this.handleImport(type, curPkg, list);
			}
		}
		List<ManyToMany> mtmList = getModelMtmList(model);
		for(ManyToMany mtm:mtmList){
			type = mtm.getRelation();
			this.handleImport(type, curPkg, list);
			type = mtm.getAnotherModel();
			this.handleImport(type, curPkg, list);
		}
		
		return list;
	}
	
	private void handleImport(String type, String curPkg, List<String> list){
		if(type==null){
			return;
		}
		String regex = "([^<>]+)(?:<(\\S+)>)?";
		Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE|Pattern.DOTALL);
		Matcher matcher = pattern.matcher(type);
		String[] types;
		if(matcher.matches()){
			types = new String[]{matcher.group(1),matcher.group(2)};
		}else{
			types = new String[]{type};
		}
		for(String type1:types){
			if(type1==null){
				continue;
			}
			if("void".equals(type1)||this.isPrimitive(type1)){
				continue;
			}
			String pkg = this.getPackage(type1);
			if(pkg==null||pkg.isEmpty()){
				type1 = curPkg+'.'+type1;
			}
			if(!list.contains(type1)){
				list.add(type1);
			}
		}
	}
	
	public boolean isPrimitive(String type){
		return ModelDataType.isPrimitive(type);
	}
	
	public boolean checkModifier(Method method, String modifier){
		String mm = method.getModifiers();
		if(mm==null||mm.isEmpty()){
			return "package".equals(modifier);
		}
		return mm.indexOf(modifier)!=-1;
	}

	private String getPackage(String type){
		if(type==null){
			return null;
		}
		int pos =  type.lastIndexOf('.');
		if(pos==-1){
			return null;
		}
		return type.substring(0, pos);
	}
	
	public List<ModelField> getReferenceFields(Model model){
		List<ModelField> list = this.findField(model, "reference!=null", true, false);
		if(this.isRelationModel(model)){
			ManyToMany mtm = this.getElementMtm(model);
			if(mtm!=null){
				String fname1 = mtm.getRelationKey();
				String fname2 = mtm.getAnotherRelationKey();
				ModelField field1 = this.findFieldByName(model, fname1);
				if(field1!=null && !list.contains(field1)){
					list.add(field1);
				}
				ModelField field2 = this.findFieldByName(model, fname2);
				if(field2!=null && !list.contains(field2)){
					list.add(field2);
				}
			}
		}
		return list;
	}
	
	public Reference getReference(Model model, ModelField field){
		if(field.getReference()!=null){
			return field.getReference();
		}
		if(field.getAttributes()!=null && field.getAttributes().get("ref")!=null){
			String str = (String) field.getAttributes().get("ref");
			String strArr[] = str.split("\\.");
			Reference ref = new Reference();
			if(strArr.length==2){
				ref.setModelName((model.getPackage()!=null?model.getPackage():"")+'.'+strArr[0]);
			}
			if(strArr.length>2){
				ref.setModelName(strArr[0]);
			}
			ref.setFiledName(strArr[strArr.length-1]);
			return ref;
		}
		return null;
	}
	
	public Object getRefEnum(Reference reference){
		if(reference==null||reference.getModelName()==null){
			return null;
		}
		Model refModel = findModel(reference.getModelName());
		if(reference.getFiledName()==null){
			return refModel;
		}
		if(refModel==null||refModel.getStaticFields()==null||refModel.getStaticFields().size()==0){
			return null;
		}
		Map map = new LinkedHashMap();
		String prefix = reference.getFiledName();
		prefix = prefix.replaceAll("\\*", "");
		for(ModelField field:refModel.getStaticFields()){
			if(!isPrimitive(field.getType())){
				continue;
			}
			if(field.getName().startsWith(prefix)){
				String label = field.getLabel();
				if(label==null){
					label = field.getName();
				}
				map.put(field.getDefaultValue(),label);
			}
		}
		return map;
	}
	
	public Object getParent(Model model){
		String parent = model.getParent();
		Model parentModel = findModel(parent);
		return parentModel;
	}
	
	public Object getIdField(Model model){
		if(model!=null && model.getFields()!=null){
			for(ModelField field:model.getFields()){
				if(Boolean.TRUE.equals(field.getId())){
					return field;
				}
			}
		}
		return null;
	}
	
	public boolean isNullable(ModelField field){
		if(field.getNullable()==null){
			return true;
		}
		return field.getNullable().booleanValue();
	}

	public List<ModelField> findAllField(Model model){
		return this.findAllField(model, false);
	}
	
	//TODO
	public Iterator<ModelField> allFieldIterator(Model model){
		Iterator<ModelField> it = new Iterator<ModelField>(){
			public boolean hasNext() {
				return false;
			}
			public ModelField next() {
				return null;
			}
			public void remove() {
			}
		};
		return null;
	}
	
	public List<ModelField> findAllField(Model model, Boolean sort){
		List<ModelField> list = new ArrayList<ModelField>();
		Model cur = model;
		while(cur!=null){
			list.addAll(cur.getFields());
			String parent = cur.getParent();
			if(parent!=null && ctx!=null){
				cur = findModel(parent);
			}else{
				cur = null;
			}
		}
		if(Boolean.TRUE.equals(sort)){
			sort(list, list);
		}
		return list;
	}
	
	public List<ModelField> findField(Model model, String mql){
		return findField(model, mql, true, false);
	}
	
	public ModelField findOneField(Model model, String mql, Boolean sort, Boolean inherited){
		List<ModelField> list = this.findField(model, mql, sort, inherited);
		if(list.size()>0){
			return list.get(0);
		}
		return null;
	}
	
	public ModelField findFieldByName(Model model, String name){
		List<ModelField> list = this.findField(model, "name='"+name+"'", true, true);
		if(list.size()>0){
			return list.get(0);
		}
		return null; 
	}
	/**
	 * 查询字段
	 * @param mql ,例子:name=aaa,reference.table=aaaa,label like xxx,
	 * @return
	 */
	public List<ModelField> findField(Model model, String mql, Boolean sort, Boolean inherited){
		List<ModelField> list = model.getFields();
		if(Boolean.TRUE.equals(inherited)){
			list = findAllField(model, sort);
		}
		List<ModelField> result = new ArrayList<ModelField>();
		for(ModelField field:list){
			if(this.checkBeanExp(field, mql)){
				result.add(field);
			}
		}
		return result;
	}
	
	public List<ModelField> getSortedList(List<ModelField> list){
		List<ModelField> newList = new ArrayList<ModelField>();
		if(list!=null){
			this.sort(list, newList);	
		}
		return newList;
	}
	
	/*
	 * 按重要性排列Field 
	 * @param list
	 */
	private void sort(List<ModelField> list, List<ModelField> newList){
		TreeMap<String,ModelField> map = new TreeMap<String,ModelField>();
		for(int i=0;i<list.size();i++){
			ModelField field = list.get(i);
			String sortNo = "9";
			if(Boolean.TRUE.equals(field.getId()) || Boolean.TRUE.equals(field.getAuto())){
				sortNo = "0";
			}else if(field.getReference()!=null){
				sortNo = "1";
			}else if(Boolean.TRUE.equals(field.getUnique())){
				sortNo = "2";
			}else if(Boolean.TRUE.equals(field.getNullable())){
				sortNo = "3";
			}
			if(ModelDataType.LONG.equals(field.getType())){
				sortNo += "A";
			}else if(ModelDataType.INTEGER.equals(field.getType())){
				sortNo += "B";
			}else if(ModelDataType.SHORT.equals(field.getType())){
				sortNo += "C";
			}else if(ModelDataType.DOUBLE.equals(field.getType())||ModelDataType.FLOAT.equals(field.getType())){
				sortNo += "D";
			}else{
				sortNo += "Z";
			}
			
			sortNo += ("-"+i);
			
			map.put(sortNo, field);
		}
		
		newList.clear();
		newList.addAll(map.values());
	}

	public static boolean checkBeanExp(Object obj, String mql) {
		String REG_VARIABLE="[a-z_A-Z\\$\\.]+";
		String REGEX = "("+REG_VARIABLE+")\\s*(=|!=|like)\\s*([^, ]+)";
		Pattern pattern = Pattern.compile(REGEX, Pattern.MULTILINE|Pattern.DOTALL);
		Matcher matcher = pattern.matcher(mql);
		boolean result = true;
		boolean find = false;
		while(matcher.find()){
			find = true;
			String name = matcher.group(1);
			String opt = matcher.group(2); 
			String val = matcher.group(3);
			Object pv = BeanUtil.getNestedProperty(obj, name);
			String pv_str = "" + pv;
			if(pv instanceof String){
				pv_str = '\''+pv_str+'\'';
			}

			boolean eval = false;
			if("=".equals(opt)){
				eval = val.equals(pv_str);
			}else if("!=".equals(opt)){
				eval = !val.equals(pv_str);
			}else if("like".equals(opt)){
				eval = pv_str.indexOf(val)!=-1;
			}
			
			result = result && eval;
		}
		return find && result;
	}
	
	public List<ManyToMany> findMtmList(Model model, String fullModelName){
		List<ManyToMany> result = new ArrayList<ManyToMany>();
		List<ManyToMany> mtmList = this.getModelMtmList(model);
		if(mtmList==null || mtmList.size()==0){
			return result;
		}
		for(ManyToMany m2m:mtmList){
			if(fullModelName.equals(m2m.getModel())){
				result.add(m2m);
			}else if(fullModelName.equals(m2m.getAnotherModel())){
				ManyToMany m2m2 = m2m.clone();
				m2m2 .switchModel();
				result.add(m2m2);
			}
		}
		return result;
	}

	public ManyToMany getElementMtm(Element element){
		List<ManyToMany> mtmList = this.getElementMtmList(element);
		if(mtmList.size()>0){
			return mtmList.get(0);
		}
		return null;
	}
	
	public List<ManyToMany> getModelMtmList(Model model){
		List<ManyToMany> mtmList = new ArrayList<ManyToMany>();
		mtmList.addAll(this.getElementMtmList(model));
		for(ModelField field:model.getFields()){
			mtmList.addAll(this.getElementMtmList(field));
		}
		for(Method method:model.getMethods()){
			mtmList.addAll(this.getElementMtmList(method));
		}
		return mtmList;
	}

	public List<ManyToMany> getElementMtmList(Element element){
		List<ManyToMany> list = new ArrayList<ManyToMany>();
		Map attrs = element.getAttributes();
		if(attrs==null){
			return list;
		}
		Object mtmObj = attrs.get("ManyToMany");
		if(mtmObj==null){
			return list;
		}
		if(List.class.isAssignableFrom(mtmObj.getClass())){
			List list2 = (List) attrs.get("ManyToMany");
			for(Object mo:list2){
				if(mo instanceof ManyToMany){
					list.add((ManyToMany) mo);
				}
			}
		}else if(mtmObj instanceof ManyToMany){
			list.add((ManyToMany) mtmObj);
		}
		for(ManyToMany mtm:list){
			//mtm.setAttribute("element", element);
		}
		return list;
	} 
	
	public boolean isStaticRef(Reference reference){
		if(reference.getFiledName().indexOf('*')!=-1){
			return true;
		}
		Model model = findModel(reference.getModelName());
		List<ModelField> flist = this.findField(model, "name='"+reference.getFiledName()+"'");
		if(flist.size()==0){
			throw new RuntimeException("field "+reference.getFiledName()+" does not exist!");
		}
		ModelField f = flist.get(0);
		return this.isStatic(f);
	}
	
	public boolean isStatic(Element element){
		if(element.getAttributes()==null){
			return false;
		}
		Object b = element.getAttributes().get("isStatic");
		return Boolean.TRUE.equals(b);
	}
	
	public List<Model> getRefModelList(Model model){
		List<Model> list = new ArrayList<Model>();
		for(ModelField field:this.findAllField(model)){
			Reference ref = field.getReference();
			if(ref==null){
				continue;
			}
			String refModelName = ref.getModelName();
			Model refModel = findModel(refModelName);
			if(refModel!=null && !list.contains(refModel)){
				list.add(refModel);
			}
		}
		List<ManyToMany> mtmList = this.getModelMtmList(model);
		for(ManyToMany mtm:mtmList){
			String[] mNames = new String[]{mtm.getModel(),mtm.getRelation(),mtm.getAnotherModel()};
			for(String refModelName:mNames){
				Model refModel = findModel(refModelName);
				if(refModel!=null && !model.equals(refModel) && !list.contains(refModel)){
					list.add(refModel);
				}
			}
		}
		return list;
	}
	
	public boolean isRelationModel(Model model){
		List<ManyToMany> list = this.getModelMtmList(model);
		if(list==null||list.isEmpty()){
			return false;
		}
		CodeStringUtil csu = new CodeStringUtil();
		for(ManyToMany mtm:list){
			if(model.getName().equals(csu.getSampleName(mtm.getRelation()))){
				return true;
			}
		}
		return false;
	}
	
	public boolean haveAtribute(Element element, String name){
		if(element==null||element.getAttributes()==null){
			return false;
		}
		return element.getAttributes().containsKey(name);
	}
	
	public static final void main(String args[]){
		ModelUtil mu = new ModelUtil();
		ModelField f = new ModelField();
		f.setName("aaa");
		f.setLength(10);
		Reference reference = new Reference();
		reference.setFiledName("fff");
		f.setReference(reference);
//		boolean b = checkMql(f, "name=aaa,reference.filedName=fff");
//		System.out.println(b);
		List<ModelField> list = new ArrayList<ModelField>();
		list.add(f);
		ModelField f2 = new ModelField();
		f2.setId(true);
		list.add(f2);
		mu.sort(list, list);
		
		System.out.println("=====");
		System.out.println(new CodeStringUtil().getVariable("com.scaffold.authority.entity.Role"));
		
		//System.out.println(ModelUtil.checkBeanExp(f, "name='aaa', format=null, length=10"));
		
		Model model = new Model();
		model.setMethods(new ArrayList());
		new ModelTrans().loadModel(model , "E:/temp/tout/model/User.xml");
		System.out.println(mu.getImportList(model));
		System.out.println(mu.findMtmList(model, "com.scaffold.authority.entity.User"));
		
	}

	public void setContext(ModelContext ctx) {
		this.ctx = ctx;
	}
	
	public String getMediaType(Element element){
		Map attrs = element.getAttributes();
		if(attrs!=null){
			if(attrs.containsKey("media")){
				return (String) attrs.get("media");
			}
			if(attrs.containsKey("img")){
				return "img";
			}
			if(attrs.containsKey("audio")){
				return "audio";
			}
			if(attrs.containsKey("video")){
				return "video";
			}
		}
		return null;
	}
}
